﻿using CleanArchitecture.Core.Events;
using CleanArchitecture.Core.Handlers;
using CleanArchitecture.Core.Interfaces;
using CleanArchitecture.Core.SharedKernel;
using CleanArchitecture.Infrastructure.Entities;
using CleanArchitecture.Infrastructure.Entities.Business;
using CleanArchitecture.Infrastructure.Interfaces;
using CleanArchitecture.Infrastructure.Interfaces.Business;
using Masuit.LuceneEFCore.SearchEngine.Interfaces;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;

namespace CleanArchitecture.Infrastructure.Services.Business
{
    /// <summary>
    /// News service
    /// </summary>
    public partial class NewsService : INewsService
    {
        #region Fields

        private readonly IDomainEventDispatcher _eventDispatcher;
        //private readonly IRepository<NewsComment> _newsCommentRepository;
        private readonly IRepository<NewsItem> _newsItemRepository;
        private readonly ILuceneIndexer _luceneIndexer;
        private readonly IRepository<AdminRoleBusColumnMapping> _rolecolumnMappingRespository;
        #endregion

        #region Ctor

        public NewsService(IDomainEventDispatcher eventDispatcher,
            //IRepository<NewsComment> newsCommentRepository,
            IRepository<NewsItem> newsItemRepository,
            IRepository<AdminRoleBusColumnMapping> rolecolumnMappingRepo,
            ILuceneIndexer luceneIndexer)
        {
            _eventDispatcher = eventDispatcher;
            _newsItemRepository = newsItemRepository;
            _rolecolumnMappingRespository = rolecolumnMappingRepo;
            _luceneIndexer = luceneIndexer;
        }

        #endregion

        #region Methods

        #region News

        /// <summary>
        /// Deletes a news
        /// DavidLee 2019/11/28 所有业务模型原则上不删除数据库对应数据，而是置为不可用
        /// </summary>
        /// <param name="newsItem">News item</param>
        /// <param name="logEvent">Whether to log Delete Event</param>
        public virtual void DeleteNewsBatch(IList<NewsItem> newsItems, bool logEvent = true)
        {
            if (newsItems == null || newsItems.Count == 0)
                throw new ArgumentNullException(nameof(newsItems));

            //_newsItemRepository.Delete(newsItem);
            string logMsg = $"批量删除新闻记录{newsItems.Count}条，ID：";
            foreach (var newsItem in newsItems)
            {
                newsItem.Title = "Deleted-" + newsItem.Title;
                newsItem.IsActive = EntityEnumConfig.YesorNo.No;
                logMsg += newsItem.Id.ToString() + ",";
                _luceneIndexer.Delete(newsItem);
            }
            _newsItemRepository.Update(newsItems);

            //event notification
            if (logEvent)
            {
                var entity = new NewsItem();
                entity.SysLogLvl = Core.Configuration.SystemLogLevel.Information;
                entity.SysLogMsg = logMsg;
                _eventDispatcher.Dispatch(new EntityDeletedEvent<NewsItem>(entity));
            }
        }

        /// <summary>
        /// Gets a news
        /// DavidLee 2020/07/08 增加是否返回文章内容，
        ///                     有时只是获取文章简单信息(例：获取文章的上一篇及下一篇)，不需要文章内容增加数据传输
        /// </summary>
        /// <param name="newsId">The news identifier</param>
        /// <param name="showContents">增加是否返回文章内容</param>
        /// <returns>News</returns>
        public virtual NewsItem GetNewsItemById(int newsId, bool showContents = true)
        {
            if (newsId == 0)
                return null;
            var newsItem = _newsItemRepository.GetById(newsId);
            if (newsItem != null && !showContents)
                newsItem.Contents = string.Empty;
            return newsItem;//_newsItemRepository.GetById(newsId);
        }

        /// <summary>
        /// Gets top N news by Column Id
        /// </summary>
        /// <param name="newsId">The news identifiers</param>
        /// <param name="columnId">columnId that newsitem belongs with</param>
        /// <param name="excludeIds">the results items without exludeIds</param>
        /// <returns>News</returns>
        public virtual IList<NewsItem> GetNewsItemListByColumnId(int columnId, int topN = 1, int[] excludeIds = null)
        {
            var query = _newsItemRepository.Table.Where(n => n.FKColumnId == columnId && n.IsActive == EntityEnumConfig.YesorNo.YES);
            if (excludeIds != null) query = query.Where(n => !excludeIds.Contains(n.Id));
            return query.OrderBy(n => n.DisplayOrder).ThenByDescending(n => n.CreatedOnTimeUtc).Take(topN).ToList();
        }
        /// <summary>
        /// 根据当前文章ID获取同栏目上一篇文章及下一篇文章
        /// </summary>
        /// <param name="curNewsId"></param>
        /// <param name="curColumnId"></param>
        /// <returns></returns>
        public virtual (NewsItem, NewsItem) GetPrivateandNextNewsItems(int curNewsId, int curColumnId = 0)
        {
            if (curNewsId <= 0) return (null, null);
            var query = _newsItemRepository.Table.OrderBy(n => n.CreatedOnTimeUtc).Where(n => n.IsActive == EntityEnumConfig.YesorNo.YES);
            if (curColumnId > 0) query = query.Where(n => n.FKColumnId == curColumnId);
            int prevNewsId = (from p in query
                              where p.Id < curNewsId
                              orderby p.Id descending
                              select p.Id).FirstOrDefault();
            int nextNewsId = (from p in query
                              where p.Id > curNewsId
                              orderby p.Id ascending
                              select p.Id).FirstOrDefault();//.FirstOrDefault()?.Id ?? 0;
            //DavidLee 2020/07/08 EF Core目前不支持以下写法 或者是我写的不对
            //https://stackoverflow.com/questions/7542021/how-to-get-max-value-of-a-column-using-entity-framework
            //int prevNewsId = query.Where(n => n.Id < curNewsId)?.Select(n => n.Id).DefaultIfEmpty(0).Max() ?? 0;
            //int nextNewsId = query.Where(n => n.Id > curNewsId)?.Select(n => n.Id).DefaultIfEmpty(0).Min() ?? 0;
            return (GetNewsItemById(prevNewsId, false), GetNewsItemById(nextNewsId, false));
        }
        /// <summary>
        /// Gets all news By Page
        /// DavidLee 2019/11/28 
        /// 在新闻列表加载时只加载部分信息，不全量加载，文章内容为html文本内容，全量加载会很慢
        /// 只有在编辑文章时才全量加载指定文章所有内容,所以在实现时只返回linq列表，不要ToList去执行SQL查询
        /// </summary>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <returns>News items</returns>
        public virtual IViewPagedList<NewsItem> GetNewsItemsByPage(int columnId = 0, int[] roleIds = null, int pageIndex = 0, int pageSize = int.MaxValue)
        {
            //var queryOnlyMe = _newsItemRepository.Table.Where(n => n.IsActive == EntityEnumConfig.YesorNo.YES 
            //                                                    && n.WhoCanSee == EntityEnumConfig.WhoCanSee.OnlyMe
            //                                                    && n.UserId.HasValue && n.UserId == userId);
            //var queryEveryOne = _newsItemRepository.Table.Where(n => n.IsActive == EntityEnumConfig.YesorNo.YES
            //                                                    && n.WhoCanSee == EntityEnumConfig.WhoCanSee.EveryOne);
            var query = _newsItemRepository.TableNoTracking;
            if (columnId > 0) query = query.Where(n => n.FKColumnId == columnId);
            //query = query.Where(n => n.IsActive == EntityEnumConfig.YesorNo.YES)
            //                               .OrderByDescending(n => n.IsRecommend)
            //                               .ThenBy(n => n.DisplayOrder)
            //                               .ThenByDescending(n => n.UpdatedOnTimeUtc);

            //DavidLee 2020/07/31 增加按角色权限查询数据功能
            if (roleIds != null)
            {
                query = from n in query
                        join ca in _rolecolumnMappingRespository.Table
                        on n.FKColumnId equals ca.FKColumnId into templeftjoin
                        from m in templeftjoin.DefaultIfEmpty()
                        where m.IsAllow == EntityEnumConfig.YesorNo.YES && roleIds.Contains(m.FKAdminRoleId)
                        select n;
            }
            //DaivdLee 2020/09/02 优化查询SQL 增加推荐/置顶文章放置列表最上方功能
            query = (from n in query
                     where n.IsActive == EntityEnumConfig.YesorNo.YES && n.IsRecommend == EntityEnumConfig.YesorNo.YES
                     select n)
                    .Union(
                     from n in query
                     where n.IsActive == EntityEnumConfig.YesorNo.YES && n.IsRecommend == EntityEnumConfig.YesorNo.No
                     select n);

            var queryWithOrderBy = ApplyOrderBy(query.Distinct(),
                    new OrderByExpression<NewsItem, EntityEnumConfig.YesorNo>(u => u.IsRecommend, true), //先置顶推荐倒序
                    new OrderByExpression<NewsItem, int>(u => u.DisplayOrder), //再排序号正序
                    new OrderByExpression<NewsItem, DateTime>(u => u.CreatedOnTimeUtc, true) //再创建时间倒序
                    );  // an int, desc
            #region 查询方法可以参考
            //if (storeId > 0 && !_catalogSettings.IgnoreStoreLimitations)
            //{
            //    query = from n in query
            //            join sm in _storeMappingRepository.Table
            //            on new { c1 = n.Id, c2 = nameof(NewsItem) } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into n_sm
            //            from sm in n_sm.DefaultIfEmpty()
            //            where !n.LimitedToStores || storeId == sm.StoreId
            //            select n;

            //    query = query.Distinct().OrderByDescending(n => n.StartDateUtc ?? n.CreatedOnUtc);
            //}
            #endregion
            var news = new ViewPagedList<NewsItem>(queryWithOrderBy, pageIndex, pageSize);
            return news;
        }

        public IQueryable<TEntity> ApplyOrderBy<TEntity>(IQueryable<TEntity> query,
                params IOrderByExpression<TEntity>[] orderByExpressions)
                where TEntity : BaseEntity
        {
            if (orderByExpressions == null)
                return query;

            IOrderedQueryable<TEntity> output = null;

            foreach (var orderByExpression in orderByExpressions)
            {
                if (output == null)
                    output = orderByExpression.ApplyOrderBy(query);
                else
                    output = orderByExpression.ApplyThenBy(output);
            }

            return output ?? query;
        }
        /// <summary>
        /// 根据栏目Id获取栏目下对应的文章
        /// 适用于栏目为单页内容展示
        /// </summary>
        /// <param name="columnId"></param>
        /// <returns></returns>
        public virtual NewsItem GetNewsItemByColumnId(int columnId)
        {
            if (columnId <= 0) return null;
            var newsItem = _newsItemRepository.Table.FirstOrDefault(n => n.FKColumnId == columnId && n.IsActive == EntityEnumConfig.YesorNo.YES);
            return newsItem;
        }
        /// <summary>
        /// 获取每个栏目下推荐/置顶的前N条文章/新闻
        /// </summary>
        /// <param name="columnIds"></param>
        /// <param name="topN"></param>
        /// <returns></returns>
        public virtual IList<NewsItem> GetRecommendNewsItemsByColumnIds(int[] columnIds, int topN = 1)
        {
            if (columnIds == null) return null;
            else if (columnIds.Length == 0) return null;
            var newsItems = from a in _newsItemRepository.Table
                            where columnIds.Contains(a.FKColumnId) &&
                            a.IsActive == EntityEnumConfig.YesorNo.YES &&
                            a.IsRecommend == EntityEnumConfig.YesorNo.YES &&
                            (from b in _newsItemRepository.Table
                             where b.FKColumnId == a.FKColumnId && b.CreatedOnTimeUtc >= a.CreatedOnTimeUtc
                             select b).Count() <= topN
                            orderby a.FKColumnId ascending
                            orderby a.CreatedOnTimeUtc descending
                            select a;
            return newsItems.ToList();
        }
        /// <summary>
        /// 根据栏目菜单Id获取每个栏目下前N条文章信息
        /// TEST: SELECT a.* FROM bus_news AS a WHERE 
        ///       (SELECT COUNT(*) FROM bus_news AS b
        ///       WHERE b.FK_ColumnId = a.FK_ColumnId AND b.UpdatedOnTimeUtc >= a.UpdatedOnTimeUtc) <= 2 
        ///       ORDER BY a.FK_ColumnId ASC, a.CreatedOnTimeUtc DESC
        /// </summary>
        /// <param name="columnIds">Arrays of Column Id</param>
        /// <param name="topN">Number of NewsItem of each group column, Default: 1</param>
        /// <returns></returns>
        public virtual IList<NewsItem> GetNewsItemsByColumnIds(int[] columnIds, int topN = 1)
        {
            if (columnIds == null) return null;
            else if (columnIds.Length == 0) return null;
            var newsItems = from a in _newsItemRepository.Table
                            where columnIds.Contains(a.FKColumnId) && a.IsActive == EntityEnumConfig.YesorNo.YES &&
                            (from b in _newsItemRepository.Table
                             where b.FKColumnId == a.FKColumnId && b.UpdatedOnTimeUtc >= a.UpdatedOnTimeUtc
                             select b).Count() <= topN
                            orderby a.FKColumnId ascending
                            orderby a.CreatedOnTimeUtc descending
                            select a;
            //if(newsItems != null && !isContainContents)
            //{
            //    newsItems.ForEachAsync((item) =>
            //    {
            //        item.Contents = string.Empty;
            //    });
            //}
            return newsItems == null ? null : newsItems.ToList();
        }
        public virtual int StatisticsNewsCount()
        {
            var newsCount = (from news in _newsItemRepository.TableNoTracking
                             where news.IsActive == EntityEnumConfig.YesorNo.YES
                             select news).Count();
            return newsCount;
        }
        /// <summary>
        /// Inserts a news item
        /// </summary>
        /// <param name="news">News item</param>
        public virtual void InsertNews(NewsItem news)
        {
            if (news == null)
                throw new ArgumentNullException(nameof(news));

            _newsItemRepository.Insert(news);
            _luceneIndexer.Add(news); //单纯的只添加索引库
            //event notification
            if (!string.IsNullOrEmpty(news.SysLogMsg))
                _eventDispatcher.Dispatch(new EntityInsertedEvent<NewsItem>(news));
        }

        /// <summary>
        /// Updates the news item
        /// </summary>
        /// <param name="news">News item</param>
        public virtual void UpdateNews(NewsItem news)
        {
            if (news == null)
                throw new ArgumentNullException(nameof(news));

            _newsItemRepository.Update(news/*, new string[] { nameof(news.CreatedOnTimeUtc) }*/);
            _luceneIndexer.Update(news);
            //event notification
            if (!string.IsNullOrEmpty(news.SysLogMsg))
                _eventDispatcher.Dispatch(new EntityUpdatedEvent<NewsItem>(news));
        }

        /// <summary>
        /// Get a value indicating whether a news item is available now (availability dates)
        /// </summary>
        /// <param name="newsItem">News item</param>
        /// <param name="dateTime">Datetime to check; pass null to use current date</param>
        /// <returns>Result</returns>
        public virtual bool IsNewsAvailable(NewsItem newsItem, DateTime? dateTime = null)
        {
            if (newsItem == null)
                throw new ArgumentNullException(nameof(newsItem));

            if (!dateTime.HasValue) dateTime = DateTime.UtcNow;
            if (newsItem.StartTaskTimeUtc.HasValue && newsItem.StartTaskTimeUtc.Value >= dateTime)
                return false;

            if (newsItem.IsActive == EntityEnumConfig.YesorNo.No)
                return false;

            return true;
        }
        #endregion

        #region News comments

        ///// <summary>
        ///// Gets all comments
        ///// </summary>
        ///// <param name="customerId">Customer identifier; 0 to load all records</param>
        ///// <param name="storeId">Store identifier; pass 0 to load all records</param>
        ///// <param name="newsItemId">News item ID; 0 or null to load all records</param>
        ///// <param name="approved">A value indicating whether to content is approved; null to load all records</param> 
        ///// <param name="fromUtc">Item creation from; null to load all records</param>
        ///// <param name="toUtc">Item creation to; null to load all records</param>
        ///// <param name="commentText">Search comment text; null to load all records</param>
        ///// <returns>Comments</returns>
        //public virtual IList<NewsComment> GetAllComments(int customerId = 0, int storeId = 0, int? newsItemId = null,
        //    bool? approved = null, DateTime? fromUtc = null, DateTime? toUtc = null, string commentText = null)
        //{
        //    var query = _newsCommentRepository.Table;

        //    if (approved.HasValue)
        //        query = query.Where(comment => comment.IsApproved == approved);

        //    if (newsItemId > 0)
        //        query = query.Where(comment => comment.NewsItemId == newsItemId);

        //    if (customerId > 0)
        //        query = query.Where(comment => comment.CustomerId == customerId);

        //    if (storeId > 0)
        //        query = query.Where(comment => comment.StoreId == storeId);

        //    if (fromUtc.HasValue)
        //        query = query.Where(comment => fromUtc.Value <= comment.CreatedOnUtc);

        //    if (toUtc.HasValue)
        //        query = query.Where(comment => toUtc.Value >= comment.CreatedOnUtc);

        //    if (!string.IsNullOrEmpty(commentText))
        //        query = query.Where(c => c.CommentText.Contains(commentText) || c.CommentTitle.Contains(commentText));

        //    query = query.OrderBy(nc => nc.CreatedOnUtc);

        //    return query.ToList();
        //}

        ///// <summary>
        ///// Gets a news comment
        ///// </summary>
        ///// <param name="newsCommentId">News comment identifier</param>
        ///// <returns>News comment</returns>
        //public virtual NewsComment GetNewsCommentById(int newsCommentId)
        //{
        //    if (newsCommentId == 0)
        //        return null;

        //    return _newsCommentRepository.GetById(newsCommentId);
        //}

        ///// <summary>
        ///// Get news comments by identifiers
        ///// </summary>
        ///// <param name="commentIds">News comment identifiers</param>
        ///// <returns>News comments</returns>
        //public virtual IList<NewsComment> GetNewsCommentsByIds(int[] commentIds)
        //{
        //    if (commentIds == null || commentIds.Length == 0)
        //        return new List<NewsComment>();

        //    var query = from nc in _newsCommentRepository.Table
        //                where commentIds.Contains(nc.Id)
        //                select nc;
        //    var comments = query.ToList();
        //    //sort by passed identifiers
        //    var sortedComments = new List<NewsComment>();
        //    foreach (var id in commentIds)
        //    {
        //        var comment = comments.Find(x => x.Id == id);
        //        if (comment !== null)
        //            sortedComments.Add(comment);
        //    }

        //    return sortedComments;
        //}

        ///// <summary>
        ///// Get the count of news comments
        ///// </summary>
        ///// <param name="newsItem">News item</param>
        ///// <param name="storeId">Store identifier; pass 0 to load all records</param>
        ///// <param name="isApproved">A value indicating whether to count only approved or not approved comments; pass null to get number of all comments</param>
        ///// <returns>Number of news comments</returns>
        //public virtual int GetNewsCommentsCount(NewsItem newsItem, int storeId = 0, bool? isApproved = null)
        //{
        //    var query = _newsCommentRepository.Table.Where(comment => comment.NewsItemId == newsItem.Id);

        //    if (storeId > 0)
        //        query = query.Where(comment => comment.StoreId == storeId);

        //    if (isApproved.HasValue)
        //        query = query.Where(comment => comment.IsApproved == isApproved.Value);

        //    return query.Count();
        //}

        ///// <summary>
        ///// Deletes a news comment
        ///// </summary>
        ///// <param name="newsComment">News comment</param>
        //public virtual void DeleteNewsComment(NewsComment newsComment)
        //{
        //    if (newsComment == null)
        //        throw new ArgumentNullException(nameof(newsComment));

        //    _newsCommentRepository.Delete(newsComment);

        //    //event notification
        //    _eventPublisher.EntityDeleted(newsComment);
        //}

        ///// <summary>
        ///// Deletes a news comments
        ///// </summary>
        ///// <param name="newsComments">News comments</param>
        //public virtual void DeleteNewsComments(IList<NewsComment> newsComments)
        //{
        //    if (newsComments == null)
        //        throw new ArgumentNullException(nameof(newsComments));

        //    foreach (var newsComment in newsComments)
        //    {
        //        DeleteNewsComment(newsComment);
        //    }
        //}

        #endregion

        #endregion
    }
}
